第12章 数据降维之PCA¶

  • 建立模型分析特征数据时, 很可能会面临特征数据维度过大的问题

    例如, 根据已有的信用卡持有人信息及其违约数据来建立信用卡违约预测模型时, 数据可能包含申请人的收入、年龄、性别、婚姻状况等数百个维度的特征, 如果将所有特征数据都用来拟合模型, 会提高模型的复杂度, 造成过拟合风险显著增大, 且不同的特征数据间可能存在共线性, 此时就需要对数据进行降维, 以浓缩特征向量

  • 这章讲解数据降维的常见算法PCA (Principal Component Analysis, 主成分分析)的原理和代码实现以及一个人脸识别模型的实现

12.1 数据降维¶

  • 如果特征变量的数量非常多, 在训练模型之前, 往往需要进行数据降维

  • 降维的方法主要有选择特征和抽取特征两种:

    • 选择特征是从原有的特征中挑选出最佳的特征

    • 抽取特征则是将数据由高维向低维投影, 进行坐标的线性转换

      PCA即为典型的抽取特征的方法, 它不仅是对高维数据进行降维, 更重要的是经过降维去除噪声, 发现数据中的模式

12.1.1 PCA的基本原理¶

二维空间降维¶

假设在二维坐标系中有一组数据, 分别是A(1,1), B(2,2), C(3,3), 降维的目的就是把这组二维数据转换为一维数据

image.png

明显可以看到, 这组数据在 $y = x$ 这条直线上, 如果需要将这组数据从二维降至一维, 可以将直线 $y = x$ 作为新的坐标轴, 在新的坐标系中只有一条横轴 $x'$, 没有纵轴, 这样就把原来的二维数据转换为一维数据了

image-2.png

其实这里的数据降维就是对原来的二维数据进行了简单的线性组合

image-3.png

  • 在实际进行数据降维前, 需要先对特征数据做零均值化处理, 即将每个特征维度减去该特征的均值

  • 每个特征都减去均值后, 再对零均值化后的数据进行线性组合

  • 总结来说, 二维到一维的数据降维的本质就是将原始数据做零均值化处理后, 寻找合适的线性组合, 将二维数据转换为一维数据

n维数据降维¶

如果原特征变量有 $n$ 个, 那么就是 $n$ 维空间降维, $n$ 维空间降维的思路和二维空间降维的思路是一致的, 都是寻找合适的线性组合系数

例如, 将 $n$ 维数据 $(X_1, X_2, \cdots, X_n)$ 转换为一维数据, 就是寻找如下所示的线性组合系数 $(a_1, a_2, \cdots, a_n)$

$$ F_1 = a_1X_1 + a_2X_2 + \cdots + a_nX_n $$

将 $n$ 维数据 $(X_1, X_2, \cdots, X_n)$ 转换成 $k$ 维数据 $(F_1, F_2, \cdots, F_k)$ 本质也是一样的, 只是从构造1个线性组合变为构造 $k$ 个线性组合

$$ \begin{aligned} F_1 &= a_{11}X_1 + a_{12}X_2 + \cdots + a_{1n}X_n\\ F_2 &= a_{21}X_1 + a_{22}X_2 + \cdots + a_{2n}X_n\\ \cdots\\ F_k &= a_{k1}X_1 + a_{k2}X_2 + \cdots + a_{kn}X_n\\ \end{aligned} $$

即

$$ F_{m \times k} = X_{m \times n} A_{n \times k} $$

其中 $m$ 为样本数, $A_{n \times k} = (\overrightarrow{a}_1, \overrightarrow{a}_2, \cdots, \overrightarrow{a}_k)$, $\overrightarrow{a}_i$ 为 $n$ 维列向量

其中要满足的线性代数条件如下

  1. 每个主成分的系数平方和为1

    $$ ||\overrightarrow{a}_i||_2^2 = 1 $$

    ($||x||_p$ 是向量 $x$ 的 p-范数, $||x||_p = (|x_1|^p + |x_2|^p + \cdots + |x_n|^p)^{\frac{1}{p}}$)

  2. 各个主成分互不相关

    $$ Cov(F_i, F_j) = 0, \forall i \neq j $$

  3. 主成分的方差依次递减, 重要性依次递减

    $$ Var(F_1) \geqslant Var(F_2) \geqslant \cdots \geqslant Var(F_k) $$

线性推导过程:

  • 将原始 $n$ 维特征数据按行做零均值化处理后求得 $n$ 维协方差矩阵 ($n \times n$), 计算协方差矩阵的特征值及其对应的单位特征向量

  • 将特征向量按特征值从大到小排序, 使用前 $k$ 行组成的矩阵乘以原始 $n$ 维数据矩阵, 即可得到降维后的 $k$ 维数据矩阵

  • 在通过该矩阵就能获得线性组合系数, 这些线性组合系数能最大化样本方差, 使新的 $k$ 个特征互不相关

实际应用中, 只需要明白核心逻辑即可, 不需要深究其数学原理

12.1.2 PCA的代码实现¶

二维空间降维的代码实现¶

In [1]:
import numpy as np
X = np.array([[1, 1], [2, 2], [3, 3]])
X
Out[1]:
array([[1, 1],
       [2, 2],
       [3, 3]])
In [2]:
# 也可以通过pandas库来构造数据,效果一样
import pandas as pd
X = pd.DataFrame([[1, 1], [2, 2], [3, 3]])
X
Out[2]:
0 1
0 1 1
1 2 2
2 3 3
In [3]:
# 数据降维,由二维降至一维
from sklearn.decomposition import PCA
pca = PCA(n_components=1)
pca.fit(X)  # 进行降维模型训练
X_transformed = pca.transform(X)  # 进行数据降维,并赋值给X_transformed

X_transformed  # 查看降维后的结果
Out[3]:
array([[-1.41421356],
       [ 0.        ],
       [ 1.41421356]])
In [4]:
# 查看此时的维度
X_transformed.shape
Out[4]:
(3, 1)
In [5]:
# 查看降维的系数
pca.components_  
Out[5]:
array([[0.70710678, 0.70710678]])
In [6]:
# 查看线性组合表达式
a = pca.components_[0][0] 
b = pca.components_[0][1]
print(f'{a:.3f} * X + {b:.3f} * Y')
0.707 * X + 0.707 * Y

三维空间的代码实现¶

In [7]:
import pandas as pd
X = pd.DataFrame([[45, 0.8, 9120], [40, 0.12, 2600], [38, 0.09, 3042], [30, 0.04, 3300], [39, 0.21, 3500]], columns=['年龄(岁)', '负债比率', '月收入(元)'])
X
Out[7]:
年龄(岁) 负债比率 月收入(元)
0 45 0.80 9120
1 40 0.12 2600
2 38 0.09 3042
3 30 0.04 3300
4 39 0.21 3500
In [8]:
# 因为三个指标数据的量级相差较大,所以可以先进行数据归一化处理
from sklearn.preprocessing import StandardScaler
X_new = StandardScaler().fit_transform(X)

X_new  # 查看归一化后的数据
Out[8]:
array([[ 1.36321743,  1.96044639,  1.98450514],
       [ 0.33047695, -0.47222431, -0.70685302],
       [-0.08261924, -0.57954802, -0.52440206],
       [-1.73500401, -0.75842087, -0.41790353],
       [ 0.12392886, -0.15025319, -0.33534653]])
In [9]:
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_new)  # 进行降维模型训练
X_transformed = pca.transform(X_new)  # 进行数据降维,并赋值给X_transformed

X_transformed  # 查看降维后的结果
Out[9]:
array([[ 3.08724247, -0.32991205],
       [-0.52888635,  0.74272137],
       [-0.70651782,  0.33057258],
       [-1.62877292, -1.05218639],
       [-0.22306538,  0.30880449]])
In [10]:
# 查看降维的系数
pca.components_  
Out[10]:
array([[ 0.52952108,  0.61328179,  0.58608264],
       [ 0.82760701, -0.22182579, -0.51561609]])

这里就是 $ X\_transformed_{m \times 2} = X\_new_{m \times 3} \times PCA.components^T_{3 \times 2}$

对于 $X\_transformed$ 中的每一行, 第 $i$ 维的特征值等于 $X\_new$ 中对应行与 $PCA.components$ 中第 $i$ 行的内积

In [11]:
dim = ['年龄(岁)', '负债比率', '月收入(元)']
for i in pca.components_:
    formula = []
    for j in range(len(i)):
        formula.append(f'{i[j]:.3f} * {dim[j]}')
    print(" + ".join(formula))
0.530 * 年龄(岁) + 0.613 * 负债比率 + 0.586 * 月收入(元)
0.828 * 年龄(岁) + -0.222 * 负债比率 + -0.516 * 月收入(元)
In [12]:
# 如果不想显示具体的特征名称,可以采用如下的写法
dim = ['X', 'Y', 'Z']
for i in pca.components_:
    formula = []
    for j in range(len(i)):
        formula.append(f'{i[j]:.3f} * {dim[j]}')
    print(" + ".join(formula))
0.530 * X + 0.613 * Y + 0.586 * Z
0.828 * X + -0.222 * Y + -0.516 * Z

12.2 案例实战: 人脸识别模型¶

12.2.1 案例背景¶

  • 人脸识别是基于人的脸部特征信息进行身份识别的一种生物识别技术。该技术蓬勃发展,应用广泛,如人脸识别门禁系统、刷脸支付软件等

  • 人脸识别在本质上是根据每张人脸图像中不同像素点的颜色进行数据建模与判断。人脸图像的每个像素点的颜色都有不同的值,这些值可以组成人脸的特征向量,不过因为人脸图像的像素点很多,所以特征变量也很多,需要利用PCA进行数据降维

12.2.2 脸数据读取、处理与变量提取¶

1.读取人脸照片数据¶

  • 数据集中图片的文件名由4部分组成:

    • 第1部分是该张图片对应的人脸编号

    • 第2部分是固定分隔符 “_”

    • 第3部分是该张图片在该人脸10张图片中的顺序编号

    • 第4部分是文件扩展名“.jpg”

    image.png

In [13]:
# 在python中获取文件名列表

import os
names = os.listdir('olivettifaces') # listdir()函数返回指定文件夹下的文件或文件夹的名称列表

names[0:5]  # 查看前5项读取的文件名
Out[13]:
['10_0.jpg', '10_1.jpg', '10_2.jpg', '10_3.jpg', '10_4.jpg']
In [14]:
# 获取到文件名称后,便可以通过如下代码在Python中查看这些图片
from PIL import Image
img0 = Image.open('olivettifaces\\' + names[0])

# img0.show()
img0  # 在Jupyter Notebook中可以直接输入变量名查看图像
Out[14]:
No description has been provided for this image

2.人脸数据处理:特征变量提取¶

In [15]:
# 图像灰度处理及数值化处理
import numpy as np
img0 = img0.convert('L')
img0 = img0.resize((32, 32))
arr = np.array(img0)

arr  # 查看数值化后的结果
Out[15]:
array([[186,  76,  73, ..., 100, 103, 106],
       [196,  85,  68, ...,  85, 106, 103],
       [193,  69,  79, ...,  82,  99, 100],
       ...,
       [196,  87, 193, ..., 103,  66,  52],
       [219, 179, 202, ..., 150, 127, 109],
       [244, 228, 230, ..., 198, 202, 206]], dtype=uint8)
In [16]:
# 如果觉得numpy格式的arr不好观察,则可以通过pandas库将其转为DataFrame格式进行观察
import pandas as pd
pd.DataFrame(arr)
Out[16]:
0 1 2 3 4 5 6 7 8 9 ... 22 23 24 25 26 27 28 29 30 31
0 186 76 73 87 89 88 75 81 100 102 ... 71 75 75 73 76 85 95 100 103 106
1 196 85 68 78 104 97 100 94 83 87 ... 52 59 70 85 62 82 89 85 106 103
2 193 69 79 92 105 102 112 117 106 94 ... 41 45 50 76 59 74 83 82 99 100
3 186 67 71 75 85 99 114 115 109 109 ... 42 43 40 52 41 61 69 76 76 108
4 179 46 41 50 53 69 80 91 108 104 ... 43 37 30 31 35 43 59 61 56 101
5 173 33 43 49 48 53 64 69 72 75 ... 38 36 33 32 39 45 68 60 45 83
6 173 30 37 41 42 57 81 88 77 64 ... 31 32 35 32 35 49 65 64 53 87
7 171 24 32 36 42 55 77 101 107 102 ... 54 64 63 51 53 60 56 46 49 89
8 170 21 31 29 28 35 47 62 76 83 ... 105 101 89 63 45 42 41 37 61 101
9 172 21 22 27 28 30 33 43 46 44 ... 129 118 103 74 39 27 36 34 68 101
10 171 23 30 21 30 36 44 51 46 41 ... 136 126 120 102 70 40 30 35 78 101
11 175 21 33 31 42 54 71 73 64 59 ... 142 133 122 114 111 77 31 36 95 104
12 192 42 27 37 63 87 99 111 116 116 ... 105 108 108 107 109 104 58 59 100 97
13 196 82 41 58 102 112 110 110 107 108 ... 70 71 86 102 107 116 84 77 95 99
14 192 88 78 95 96 87 81 72 51 40 ... 62 73 73 101 116 116 99 80 88 105
15 190 88 102 114 99 76 55 55 50 37 ... 100 112 120 120 125 125 105 97 101 98
16 189 106 111 113 137 124 113 109 103 96 ... 134 137 142 146 134 120 96 115 122 93
17 188 106 132 119 142 160 158 153 148 145 ... 163 158 156 142 124 110 100 114 108 86
18 193 83 140 130 122 141 153 160 168 177 ... 163 158 148 129 112 103 99 99 78 77
19 190 81 117 127 107 120 134 146 163 166 ... 157 145 132 117 105 103 90 64 67 72
20 193 83 84 106 104 113 122 134 138 143 ... 142 129 116 109 105 102 86 55 60 63
21 194 78 87 91 92 108 113 122 127 140 ... 140 128 118 113 109 101 68 56 56 56
22 191 80 89 88 90 104 114 120 131 141 ... 130 129 119 108 103 101 50 53 55 53
23 189 77 89 91 86 93 111 122 133 129 ... 102 113 111 107 101 85 53 51 54 55
24 190 86 88 87 87 87 104 115 127 115 ... 105 108 104 102 97 55 53 50 53 59
25 187 74 89 81 93 130 103 96 110 108 ... 111 105 103 102 83 50 49 56 51 49
26 190 79 81 107 166 206 119 88 94 105 ... 102 104 99 100 111 111 57 48 52 53
27 192 78 83 173 211 158 114 100 87 94 ... 101 98 98 96 116 123 119 52 49 55
28 188 70 136 177 198 108 101 119 86 81 ... 98 98 93 82 80 123 145 73 43 51
29 196 87 193 187 179 113 123 123 110 81 ... 95 90 96 77 53 160 124 103 66 52
30 219 179 202 196 198 146 122 118 119 94 ... 92 90 87 57 89 126 140 150 127 109
31 244 228 230 231 233 213 188 195 193 189 ... 179 184 177 161 202 182 207 198 202 206

32 rows × 32 columns

In [17]:
# 上面获得的32*32的二维数组,还不利于数据建模,所以我们还需要通过reshape(1, -1)方法将其转换成一行(若reshape(-1,1)则转为一列),也即1*1024格式
arr = arr.reshape(1, -1)

print(arr)  # 查看转换后的结果,这一行数就是代表那张人脸图片了,其共有32*32=1024列数
[[186  76  73 ... 198 202 206]]

因为总共有400张照片需要处理,若将400个二维数组堆叠起来会形成三维数组,因为我们需要使用flatten()函数将1*1024的二维数组降维成一维数组,并通过tolist()函数将其转为列表方便之后和其他图片的颜色数值信息一起处理

In [18]:
print(arr.flatten().tolist())  # 下面这一行数就是那张人脸转换后的结果了
[186, 76, 73, 87, 89, 88, 75, 81, 100, 102, 105, 92, 74, 65, 65, 53, 43, 55, 53, 42, 58, 77, 71, 75, 75, 73, 76, 85, 95, 100, 103, 106, 196, 85, 68, 78, 104, 97, 100, 94, 83, 87, 88, 89, 86, 70, 65, 61, 55, 52, 38, 32, 52, 66, 52, 59, 70, 85, 62, 82, 89, 85, 106, 103, 193, 69, 79, 92, 105, 102, 112, 117, 106, 94, 91, 112, 101, 87, 75, 61, 58, 54, 49, 48, 44, 41, 41, 45, 50, 76, 59, 74, 83, 82, 99, 100, 186, 67, 71, 75, 85, 99, 114, 115, 109, 109, 98, 101, 86, 68, 74, 65, 58, 53, 51, 52, 42, 40, 42, 43, 40, 52, 41, 61, 69, 76, 76, 108, 179, 46, 41, 50, 53, 69, 80, 91, 108, 104, 98, 93, 91, 88, 73, 60, 56, 55, 51, 49, 53, 55, 43, 37, 30, 31, 35, 43, 59, 61, 56, 101, 173, 33, 43, 49, 48, 53, 64, 69, 72, 75, 82, 84, 84, 82, 72, 75, 69, 71, 67, 56, 58, 55, 38, 36, 33, 32, 39, 45, 68, 60, 45, 83, 173, 30, 37, 41, 42, 57, 81, 88, 77, 64, 63, 64, 65, 64, 48, 48, 68, 62, 47, 50, 45, 31, 31, 32, 35, 32, 35, 49, 65, 64, 53, 87, 171, 24, 32, 36, 42, 55, 77, 101, 107, 102, 98, 83, 71, 64, 44, 48, 54, 64, 76, 65, 49, 48, 54, 64, 63, 51, 53, 60, 56, 46, 49, 89, 170, 21, 31, 29, 28, 35, 47, 62, 76, 83, 87, 78, 53, 58, 65, 83, 90, 97, 108, 101, 97, 105, 105, 101, 89, 63, 45, 42, 41, 37, 61, 101, 172, 21, 22, 27, 28, 30, 33, 43, 46, 44, 43, 46, 50, 63, 76, 87, 95, 104, 114, 120, 122, 125, 129, 118, 103, 74, 39, 27, 36, 34, 68, 101, 171, 23, 30, 21, 30, 36, 44, 51, 46, 41, 45, 52, 61, 70, 84, 101, 112, 119, 126, 131, 134, 138, 136, 126, 120, 102, 70, 40, 30, 35, 78, 101, 175, 21, 33, 31, 42, 54, 71, 73, 64, 59, 65, 78, 95, 106, 110, 116, 122, 130, 140, 143, 141, 142, 142, 133, 122, 114, 111, 77, 31, 36, 95, 104, 192, 42, 27, 37, 63, 87, 99, 111, 116, 116, 117, 117, 121, 122, 120, 119, 120, 121, 120, 113, 107, 106, 105, 108, 108, 107, 109, 104, 58, 59, 100, 97, 196, 82, 41, 58, 102, 112, 110, 110, 107, 108, 107, 107, 115, 126, 126, 118, 98, 87, 69, 57, 55, 55, 70, 71, 86, 102, 107, 116, 84, 77, 95, 99, 192, 88, 78, 95, 96, 87, 81, 72, 51, 40, 52, 53, 68, 89, 116, 111, 83, 61, 44, 55, 61, 42, 62, 73, 73, 101, 116, 116, 99, 80, 88, 105, 190, 88, 102, 114, 99, 76, 55, 55, 50, 37, 60, 53, 49, 84, 161, 155, 109, 94, 83, 76, 84, 88, 100, 112, 120, 120, 125, 125, 105, 97, 101, 98, 189, 106, 111, 113, 137, 124, 113, 109, 103, 96, 84, 84, 99, 123, 172, 176, 139, 130, 127, 120, 116, 115, 134, 137, 142, 146, 134, 120, 96, 115, 122, 93, 188, 106, 132, 119, 142, 160, 158, 153, 148, 145, 156, 151, 139, 139, 160, 177, 151, 138, 141, 153, 162, 164, 163, 158, 156, 142, 124, 110, 100, 114, 108, 86, 193, 83, 140, 130, 122, 141, 153, 160, 168, 177, 171, 154, 135, 137, 161, 176, 158, 146, 143, 142, 159, 164, 163, 158, 148, 129, 112, 103, 99, 99, 78, 77, 190, 81, 117, 127, 107, 120, 134, 146, 163, 166, 155, 134, 138, 137, 153, 164, 141, 132, 132, 127, 148, 156, 157, 145, 132, 117, 105, 103, 90, 64, 67, 72, 193, 83, 84, 106, 104, 113, 122, 134, 138, 143, 141, 138, 93, 69, 84, 86, 71, 62, 75, 106, 142, 146, 142, 129, 116, 109, 105, 102, 86, 55, 60, 63, 194, 78, 87, 91, 92, 108, 113, 122, 127, 140, 148, 154, 121, 80, 72, 66, 80, 96, 106, 112, 137, 147, 140, 128, 118, 113, 109, 101, 68, 56, 56, 56, 191, 80, 89, 88, 90, 104, 114, 120, 131, 141, 145, 150, 153, 142, 144, 124, 105, 111, 119, 121, 128, 128, 130, 129, 119, 108, 103, 101, 50, 53, 55, 53, 189, 77, 89, 91, 86, 93, 111, 122, 133, 129, 125, 126, 118, 117, 115, 111, 100, 85, 87, 86, 76, 88, 102, 113, 111, 107, 101, 85, 53, 51, 54, 55, 190, 86, 88, 87, 87, 87, 104, 115, 127, 115, 101, 88, 83, 84, 91, 96, 108, 114, 101, 98, 106, 103, 105, 108, 104, 102, 97, 55, 53, 50, 53, 59, 187, 74, 89, 81, 93, 130, 103, 96, 110, 108, 108, 129, 126, 112, 119, 108, 96, 98, 105, 110, 117, 118, 111, 105, 103, 102, 83, 50, 49, 56, 51, 49, 190, 79, 81, 107, 166, 206, 119, 88, 94, 105, 112, 116, 113, 106, 96, 92, 95, 102, 103, 107, 113, 108, 102, 104, 99, 100, 111, 111, 57, 48, 52, 53, 192, 78, 83, 173, 211, 158, 114, 100, 87, 94, 108, 114, 115, 119, 129, 146, 151, 144, 140, 133, 121, 112, 101, 98, 98, 96, 116, 123, 119, 52, 49, 55, 188, 70, 136, 177, 198, 108, 101, 119, 86, 81, 94, 105, 115, 123, 127, 128, 126, 122, 119, 109, 97, 97, 98, 98, 93, 82, 80, 123, 145, 73, 43, 51, 196, 87, 193, 187, 179, 113, 123, 123, 110, 81, 73, 81, 88, 96, 97, 95, 91, 91, 89, 86, 91, 99, 95, 90, 96, 77, 53, 160, 124, 103, 66, 52, 219, 179, 202, 196, 198, 146, 122, 118, 119, 94, 76, 73, 72, 74, 77, 73, 74, 79, 77, 83, 92, 94, 92, 90, 87, 57, 89, 126, 140, 150, 127, 109, 244, 228, 230, 231, 233, 213, 188, 195, 193, 189, 181, 173, 171, 171, 171, 168, 168, 171, 173, 178, 182, 179, 179, 184, 177, 161, 202, 182, 207, 198, 202, 206]
In [19]:
# 构造所有图片的特征变量
X = []  # 特征变量
for i in names:
    img = Image.open('olivettifaces\\' + i)
    img = img.convert('L')
    img = img.resize((32, 32))
    arr = np.array(img)
    X.append(arr.reshape(1, -1).flatten().tolist())
In [20]:
import pandas as pd
X = pd.DataFrame(X)

X.head(5)  # 查看400张图片转换后的结果
Out[20]:
0 1 2 3 4 5 6 7 8 9 ... 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
0 186 76 73 87 89 88 75 81 100 102 ... 179 184 177 161 202 182 207 198 202 206
1 196 90 97 98 98 87 101 89 65 73 ... 181 167 190 188 203 209 205 198 190 190
2 193 89 97 99 75 74 83 64 77 86 ... 178 178 156 185 195 201 206 201 189 190
3 192 84 93 89 97 89 66 60 60 57 ... 173 151 199 189 203 200 196 186 182 184
4 194 72 49 45 56 37 44 62 71 71 ... 192 194 192 176 174 224 200 218 176 168

5 rows × 1024 columns

In [21]:
print(X.shape)  # 查看此时的表格结构
(400, 1024)

3.人脸数据处理:目标变量提取¶

In [22]:
# 获取目标变量y:第一张图片演示
print(int(names[0].split('_')[0]))
10
In [23]:
# 批量获取所有图片的目标变量y
y = []  # 目标变量
for i in names:
    img = Image.open('olivettifaces\\' + i)
    y.append(int(i.split('_')[0]))
    
print(y)  # 查看目标变量,也就是对应的人员编号
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

12.2.3 据划分与降维¶

In [24]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
In [25]:
# 数据降维模型训练
from sklearn.decomposition import PCA
pca = PCA(n_components=100)
pca.fit(X_train)
Out[25]:
PCA(n_components=100)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
PCA(n_components=100)
In [26]:
# 对训练集和测试集进行数据降维
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)
In [27]:
# 我们通过如下代码验证PCA是否降维:
print(X_train_pca.shape)
print(X_test_pca.shape)
(320, 100)
(80, 100)
In [28]:
# 如果想查看此时降维后的X_train_pca和X_test_pca,可以直接将它们打印出来查看,也可以将它们转为DataFrame格式进行查看,代码如下:
pd.DataFrame(X_train_pca).head()
# pd.DataFrame(X_test_pca).head()
Out[28]:
0 1 2 3 4 5 6 7 8 9 ... 90 91 92 93 94 95 96 97 98 99
0 -843.848468 -117.990083 341.041562 -45.240791 -265.560411 243.732769 280.603779 -259.737068 -216.102415 -192.380205 ... 34.318728 2.792837 -35.842708 -6.309040 -11.577465 7.730109 40.761569 -36.876915 5.904517 21.997346
1 495.124726 -937.701751 -305.851409 4.351306 -127.743015 504.793986 389.144513 5.118524 32.353108 -103.516948 ... 29.356063 -75.233485 18.607723 -49.819543 -10.210716 17.714219 15.776035 -45.848006 -18.048762 -26.388540
2 -201.200905 623.575255 130.691212 22.627082 -427.373676 -85.786784 531.627875 95.691713 176.386343 70.070456 ... 25.791654 -34.335561 2.037512 24.364232 -75.749108 66.600540 -19.601073 -75.682701 24.363958 22.369335
3 -603.867640 -744.880158 -626.408564 -598.649870 -400.044101 7.220209 -246.688151 58.416884 417.199237 121.976573 ... -35.618443 12.447379 -21.560417 18.310737 9.675979 72.568819 21.155308 26.964040 0.463389 -58.788670
4 -935.882937 132.602933 441.577563 145.548390 -260.280190 248.876747 235.116994 -333.232315 -98.692085 -75.838789 ... 9.744077 -30.824290 -76.967401 8.543038 79.753247 3.739868 15.566506 40.380661 -90.864002 92.131989

5 rows × 100 columns

12.2.4 模型的搭建与使用¶

In [29]:
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()  # 建立KNN模型  
knn.fit(X_train_pca, y_train)  # 用降维后的训练集进行训练模型
Out[29]:
KNeighborsClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
KNeighborsClassifier()
In [30]:
y_pred = knn.predict(X_test_pca)  # 用降维后的测试集进行测试
print(y_pred)  # 将对测试集的预测结果打印出来
[ 9 21  3 40 26  4 28 37 12 36 26  7 27 21  3 24  7  2 17 24 21 32  8  2
 11 19  6 29  6 29 18 10 25 35 10 18 15  5  9 22 34 29  2 16  8 18  8 38
 39 35 16 30 30 11 37 36 35 20 33  6  1 16 31 18  5 30  1 39 35 39  2 19
  5  8 11  4 14 27 22 24]
In [31]:
# 通过和之前章节类似的代码,我们可以将预测值和实际值进行对比:
import pandas as pd
a = pd.DataFrame()  # 创建一个空DataFrame 
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)

a.head()  # 查看表格前5行
Out[31]:
预测值 实际值
0 9 9
1 21 21
2 3 3
3 40 40
4 26 26
In [32]:
# 查看预测准确度 - 方法1
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.9
In [33]:
# 查看预测准确度 - 方法2
score = knn.score(X_test_pca, y_test)
print(score)
0.9
In [34]:
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()  # 建立KNN模型  
knn.fit(X_train, y_train)  # 不使用数据降维,直接训练
y_pred = knn.predict(X_test)  # 不使用数据降维,直接测试

from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.9125

此时获得的准确度评分score为0.91,可以看到使用数据降维对提高模型预测效果还是有一些效果的,这里的数据量并不大,当数据量更大的时候,利用PCA主成分分析进行数据降维则会发挥更大的作用

12.3 人脸识别外部接口调用¶

  • 了解了人脸识别的基本原理后,本节介绍如何调用成熟的外部接口——百度人脸识别接口,进行人脸识别及人脸打分

  • 它的原理同样是通过特征提取和数据降维来训练人脸识别模型,不需要深究,只需要学习它的使用方法

12.3.1 baidu-aip库安装与准备工作¶

在此之前安装 baidu-aip 库

cmd
pip install baidu-aip
  • 先在浏览器打开百度人脸识别官网, 登录账号后进入首页

    image.png

  • 点击 "立即使用", 点击左侧的 "应用列表" 然后点击 "创建应用"

    image-3.png

  • 勾选 "人脸识别"

    image-2.png

    应用名称和应用描述随便填

  • 点击最下方的 "立即创建"

    image-4.png

  • 最后返回应用列表就可以复制创建的应用的APP_ID,API_KEY,SECRET_KEY了

12.3.2 调用接口进行人脸识别和打分¶

In [35]:
from aip import AipFace
import base64

# 下面3行内容为自己的APP_ID,API_KEY,SECRET_KEY
APP_ID = '118206171'
API_KEY = 'CfVdCEew4dkXALKfSTioCl9F'
SECRET_KEY = 'aDj692Tqpz6rOREP83mfSlm5wrOWIIet'

# 把上面输入的账号信息传入接口
aipFace = AipFace(APP_ID, API_KEY, SECRET_KEY)

# 下面一行内容为需要识别的人脸图片的地址,其他地方就不用改了
filePath = r'fc.png'

# 定义打开文件的函数
def get_file_content(filePath):
    with open(filePath, 'rb') as fp:
        content = base64.b64encode(fp.read())
        return content.decode('utf-8')
    
img = Image.open(filePath)

# 指定要固定的高度,这里以 300 像素为例
fixed_height = 200

# 计算调整后的宽度,保持纵横比
width, height = img.size
new_width = int(width * (fixed_height / height))

# 调整图片大小
resized_img = img.resize((new_width, fixed_height), Image.LANCZOS)
In [36]:
imageType = "BASE64"

# 选择最后要展示的内容,这里展示age(年龄);gender(性别);beauty(颜值)
options = {}
options["face_field"] = "age,gender,beauty"

# 调用接口aipFace的detect()函数进行人脸识别,打印结果
result = aipFace.detect(get_file_content(filePath), imageType, options)
print(result)



# 打印具体信息,本质就是列表索引和字典的键值对应
age = result['result']['face_list'][0]['age']
print('年龄预测为:' + str(age))
gender = result['result']['face_list'][0]['gender']['type']
print('性别预测为:' + gender)
beauty = result['result']['face_list'][0]['beauty']
print('颜值评分为:' + str(beauty))

resized_img
{'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 1733816931, 'timestamp': 1742892393, 'cached': 0, 'result': {'face_num': 1, 'face_list': [{'face_token': 'bd58cc3ddbaa7898acdfd83422c77b83', 'location': {'left': 90.54, 'top': 313.18, 'width': 585, 'height': 634, 'rotation': 0}, 'face_probability': 1, 'angle': {'yaw': -2.55, 'pitch': -0.84, 'roll': -1.87}, 'age': 21, 'beauty': 30.28, 'gender': {'type': 'male', 'probability': 1}}]}}
年龄预测为:21
性别预测为:male
颜值评分为:30.28
Out[36]:
No description has been provided for this image

这里只提取了3个参数age,gender,beauty, 更多参数可以查看官方文档